﻿using System;
using System.Collections.Generic;
using System.Text;

namespace SscLib
{
    /// <summary>
    /// Defines a Type symbol.
    /// Type symbols define classes and primitive types in Simula.
    /// </summary>
    public class TypeSymbol : ScopeSymbol
    {
        /// <summary>Dictionary containing symbols local to the type.</summary>
        protected Dictionary<string, Symbol> members = new Dictionary<string, Symbol>(StringComparer.CurrentCultureIgnoreCase);

        /// <summary>The inherited or base type for this Type.</summary>
        public TypeSymbol BaseType;

        /// <summary>
        /// List of implisit inspects for this class.
        /// </summary>
        protected List<InspectSymbol> inspects = new List<InspectSymbol>();

        /// <summary>
        /// The native .Net type for this type.
        /// Called system type.
        /// </summary>
        //TODO: Check if we can change this from hiding the Symbol.SystemType property to override insted.
        new public Type SystemType;

        /// <summary>
        /// Type of elements if this type is an Array Type.
        /// </summary>
        public TypeSymbol ElementType;

        /// <summary>
        /// Number of dimentions of this type is an Array Type.
        /// </summary>
        public int Dimentions;

        public bool IsArray
        {
            get { return this.Dimentions > 0; }
        }
        public System.Reflection.Emit.TypeBuilder Builder
        {
            get { return this.SystemType as System.Reflection.Emit.TypeBuilder; }
        }

        /// <summary>
        /// Constructor for TypeSymbol.
        /// </summary>
        /// <param name="name">Type name.</param>
        /// <param name="baseType">Base or inherited type.</param>
        internal TypeSymbol(string name, TypeSymbol baseType)
            : this(name, baseType, null)
        {
        }

        /// <summary>
        /// Constructor for TypeSymbol.
        /// </summary>
        /// <param name="name">Type name.</param>
        /// <param name="baseType">>Base or inherited type.</param>
        /// <param name="sysType">Native .Net type, also called system type.</param>
        internal TypeSymbol(string name, TypeSymbol baseType, Type sysType)
            : base(name, null)
        {
            this.BaseType = baseType;
            this.SystemType = sysType;
            this.Type = this;
        }

        /// <summary>
        /// Creates a vector version of this TypeSymbol.
        /// A vector is a zero-based one-dimentional array.
        /// </summary>
        /// <returns>SymboType defining the vecotr type.</returns>
        public TypeSymbol GetVectorType()
        {
            string name = this.Name + "Vector";

            //TODO: Add Array as a type to SymbolTable to be used as BaseType for all Arrays.
            TypeSymbol arrayType = new TypeSymbol(name, null);

            arrayType.Dimentions = 1;
            arrayType.ElementType = this;

            arrayType.SystemType = this.SystemType.MakeArrayType();

            arrayType.Type = arrayType;

            return arrayType;
        }

        /// <summary>
        /// Creates a array version of this TypeSymbol, with the specified number of dimentions.
        /// This is not a vector (zero-based one-dimentional array).
        /// The resulting array type can hold non-zero based arrays as well as zero-based ones.
        /// </summary>
        /// <param name="dimentions">Number of array dimentions.</param>
        /// <returns>SymboType defining the multidimentional array type.</returns>
        public TypeSymbol GetArrayType(int dimentions)
        {
            string name = this.Name + "Array" + dimentions.ToString();

            //TODO: Add Array as a type to SymbolTable to be used as BaseType for all Arrays.
            TypeSymbol arrayType = new TypeSymbol(name, null);

            arrayType.Dimentions = dimentions;
            arrayType.ElementType = this;

            //arrayType.SystemType = this.SystemType.MakeArrayType(dimentions);

            arrayType.Type = arrayType;

            return arrayType;
        }

        public TypeSymbol GetByRefType()
        {
            TypeSymbol byRefType = new TypeSymbol(this.Name, this.BaseType);

            byRefType.Type = this;

            byRefType.SystemType = this.SystemType.MakeByRefType();

            return byRefType;
        }

        public TypeSymbol GetDeRefType()
        {
            return this._type;
        }

        private string CreateUniqueName(string name)
        {
            int i = 0;
            while (this.members.ContainsKey(name + i.ToString()) == true) i++;

            name += i.ToString();

            return name;
        }

        /// <summary>
        /// AddPrefixedBlock creates and returns a TypeSymbol representing the prefixed block.
        /// </summary>
        /// <remarks>
        /// Prefixed blocks are mapped to anonymous types in Sim# with the prefix becoming the base type
        /// for the anonymous type.
        /// </remarks>
        /// <param name="baseType">Prefix for the anonymous type.</param>
        /// <returns>An anononymous type.</returns>
        public TypeSymbol AddPrefixedBlock(TypeSymbol baseType)
        {

            string anonymName = CreateUniqueName("$SS$pfb$");
            TypeSymbol anonym = this.AddType(anonymName, baseType);
            return anonym;
        }
        
        /// <summary>
        /// Returns a string that represents this Type symbol.
        /// </summary>
        /// <returns>Returns a string representation of this Type symbol.</returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("Type({0})", base.ToString());
            //sb.AppendLine("Type(" + Name + "):");
            //sb.Append(base.ToString());
            return sb.ToString();
        }

        /// <summary>
        ///  Returns the fully qualified name of this Type.
        /// </summary>
        public string TypeName
        {
            get {
            string s = null;
            if (BaseType != null) s = BaseType.TypeName + ".";
            return s + Name;
            }
        }

        /// <summary>
        /// Returns the constructor symbol for this type.
        /// </summary>
        /// <returns>The constructor symbol for this type.</returns>
        public CtorSymbol GetCtor()
        {
            CtorSymbol ctor = (CtorSymbol)this.Members[".ctor"];
            return ctor;
        }

        /// <summary>Returns a Dictionary containing Symbols defined inside this Scope.</summary>
        public override Dictionary<string, Symbol> Members
        {
            get { return this.members; }
        }

        /// <summary>
        /// Resolves the symbol name in this scope.
        /// If the symbol is not defined in this scope, then tries to resolve in the scope enclosing this scope.
        /// Types also searches the class implisit inspects for the symbol.
        /// </summary>
        /// <param name="name">Name of the symbol to resolve.</param>
        /// <returns>The resolved symbol or null.</returns>
        public override Symbol Resolve(string name)
        {
            Symbol sym = null;
            if (this.Members.ContainsKey(name))
            {
                sym = this.Members[name];
            }

            //TODO: verify if it should be possible to have an empty BaseType.
            if (sym == null && this.BaseType != null) sym = this.BaseType.Resolve(name);

            if (sym == null)
            {
                foreach (InspectSymbol inspect in this.inspects)
                {
                    sym = inspect.Resolve(name);
                    if (sym != null) break;
                }
            }

            if (sym == null && this.Scope != null) sym = this.Scope.Resolve(name);

            return sym;
        }

        /// <summary>
        /// Adds a class wide inspect to the class. 
        /// The inspected item must be a field belonging to this class.
        /// </summary>
        /// <param name="f">The Field to be inspected.</param>
        public void InspectField(FieldSymbol f)
        {

            if (!this.Members.ContainsKey(f.Name))
            {
                throw new InvalidOperationException("Inspected field does not belong to this Type");
            }
            this.inspects.Add(new InspectSymbol(f));
        }

        public override InspectSymbol GetInspectForSymbol(Symbol s)
        {
            TypeSymbol t = this;
            while (t != null)
            {
                if (t.Members.ContainsKey(s.Name)) return null;
                t = t.BaseType;
            }

            InspectSymbol i = base.GetInspectForSymbol(s);
            if (i == null)
            {
                foreach (InspectSymbol iscope in this.inspects)
                {
                    i = iscope.GetInspectForSymbol(s);
                    if (i != null) break;
                }
            }
            if (i == null && this.BaseType != null) i = this.BaseType.GetInspectForSymbol(s);
            return i;
        }
    }
}
